package net.media.datafw.database;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.media.datafw.database.model.DataBaseConfiguration;
import net.media.datafw.database.pool.ConnectionPool;

public class ConnectionManager {
	Logger logger = LoggerFactory.getLogger(getClass());

	private static final int NUM_CXN_OPEN_ATTEMPTS = 3;
	private static final int TIME_BETWEEN_ATTEMPTS = 1000;

	ConnectionPool readWriteConnectionPool;
	ConnectionPool readOnlyConnectionPool;

	public ConnectionManager(String dataBaseDriver, int maxReadWriteConnection, int maxReadOnlyConnection, DataBaseConfiguration readWriteConfiguration, DataBaseConfiguration readOnlyConfiguration) {
		try {
			this.readWriteConnectionPool = new ConnectionPool(maxReadWriteConnection, readWriteConfiguration, this);
			this.readOnlyConnectionPool = new ConnectionPool(maxReadOnlyConnection, readOnlyConfiguration, this);

			DriverManager.registerDriver((Driver) Class.forName(dataBaseDriver).newInstance());
			if (logger.isDebugEnabled()) {
				DriverManager.setLogWriter(new java.io.PrintWriter(System.out, true));
			}
		} catch (Exception e) {
			logger.error("Unable to initialize ConnectionManager", e);
		}
	}

	public DataBaseConnection getConnection(boolean isReadWrite) {
		return (isReadWrite ? readWriteConnectionPool : readOnlyConnectionPool).getConnection();
	}

	public Connection createConnection(DataBaseConfiguration configuration) {
		Connection connection = null;
		try {

			for (int i = 0; i < NUM_CXN_OPEN_ATTEMPTS; i++) {
				try {
					connection = DriverManager.getConnection(configuration.getDataBaseUrl(), configuration.getDataBaseUser(), configuration.getDataBasePassword());
					break;
				} catch (SQLException se) {
					if (i == NUM_CXN_OPEN_ATTEMPTS - 1) {
						throw se;
					}
					logger.warn("Failed to open db connection. Retrying : ", se);
					try {
						Thread.sleep(TIME_BETWEEN_ATTEMPTS);
					} catch (InterruptedException ie) {
					}
				}
			}
			connection.setAutoCommit(false);
			connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
			connection.setReadOnly(configuration.isReadOnly());
		} catch (Exception e) {
			logger.error("Unable to create Connection", e);
		}
		return connection;
	}

	public void reOpenConnection(DataBaseConnection connection) {
		try {
			Connection cxn = null;
			DataBaseConfiguration configuration = connection.getConnectionPool().getConfigeration();
			for (int i = 0; i < NUM_CXN_OPEN_ATTEMPTS; i++) {
				try {
					cxn = DriverManager.getConnection(configuration.getDataBaseUrl(), configuration.getDataBaseUser(), configuration.getDataBasePassword());
					break;
				} catch (SQLException se) {
					if (i == NUM_CXN_OPEN_ATTEMPTS - 1) {
						throw se;
					}
					logger.warn("Failed to reopen db connection. Retrying : ", se);
					try {
						Thread.sleep(TIME_BETWEEN_ATTEMPTS);
					} catch (InterruptedException ie) {
					}
				}
			}
			cxn.setAutoCommit(false);
			cxn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
			cxn.setReadOnly(configuration.isReadOnly());
			connection.setConnection(cxn);
		} catch (Exception e) {
			logger.error("Unable to reopen Connection", e);
		}
	}

	public void closeConnection(DataBaseConnection connection) {
		connection.closeConnection();
	}

	public void commit(DataBaseConnection connection) throws DataBaseException {
		if (connection != null) {
			ConnectionPool connectionPool = connection.getConnectionPool();
			try {
				connection.commit();
				connectionPool.putBack(connection);
			} catch (SQLException sqle) {
				logger.warn("WARNING: FAILED TO COMMIT TRANSACTION BCOS CXN IS CLOSED: Queries executed=" + connection.getQueriesExecuted(), sqle);
				boolean isQueriesExecuted = connection.getQueriesExecuted() > 0;
				boolean isConnectionClosed = connection.isConnectionClosed(sqle.getMessage());
				connection.setMarkedForClose(true);
				connectionPool.putBack(connection);
				if (isQueriesExecuted || !isConnectionClosed) {
					throw new DataBaseException(sqle);
				}
			}
		}
	}

	public void rollback(DataBaseConnection connection) throws DataBaseException {
		if (connection != null) {
			ConnectionPool connectionPool = connection.getConnectionPool();
			try {
				connection.rollback();
				connectionPool.putBack(connection);
			} catch (SQLException sqle) {
				logger.warn("WARNING: FAILED TO ROLLBACK TRANSACTION BCOS CXN IS CLOSED: Queries executed=" + connection.getQueriesExecuted(), sqle);
				boolean isQueriesExecuted = connection.getQueriesExecuted() > 0;
				boolean isConnectionClosed = connection.isConnectionClosed(sqle.getMessage());
				connection.setMarkedForClose(true);
				connectionPool.putBack(connection);
				if (isQueriesExecuted || !isConnectionClosed) {
					throw new DataBaseException(sqle);
				}
			}
		}
	}
}
